package com.alibaba.otter.manager.web.config.filter;

import com.google.common.base.Stopwatch;
import com.google.common.io.ByteStreams;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.multipart.support.MultipartResolutionDelegate;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

public class RequestAccessFilter implements Filter {


    private final Logger log = LoggerFactory.getLogger(getClass());


    private final PathMatcher pathMatcher = new AntPathMatcher();

    private final List<String> ignoreUrls = List.of("/actuator/health", "/management/health");

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {


        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        if (isStream(httpServletRequest) || ignoreUrls.stream().anyMatch(url -> pathMatcher.match(url, httpServletRequest.getRequestURI()))) {
            // do filter
            chain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        ContentCachingRequestWrapper cachingRequestWrapper = new ContentCachingRequestWrapper(httpServletRequest);
        ContentCachingResponseWrapper cachingResponseWrapper = new ContentCachingResponseWrapper(httpServletResponse);

        Stopwatch stopwatch = Stopwatch.createStarted();
        StringBuilder requestAccessLogBuilder = new StringBuilder();
        try {
            // do filter
            chain.doFilter(cachingRequestWrapper, cachingResponseWrapper);
        } finally {
            // request
            logRequest(cachingRequestWrapper, requestAccessLogBuilder);
            // response
            logResponse(cachingResponseWrapper, requestAccessLogBuilder);
            // release
            cachingResponseWrapper.copyBodyToResponse();
            // elapsed timed
            long elapsed = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
            requestAccessLogBuilder.append("-----> [elapsed ").append(elapsed).append("ms] <-----\n");
            log.info(requestAccessLogBuilder.toString());
        }
    }

    private boolean isStream(HttpServletRequest request) {
        return MediaType.TEXT_EVENT_STREAM_VALUE.equalsIgnoreCase(request.getHeader(HttpHeaders.ACCEPT)) ||
                MediaType.TEXT_EVENT_STREAM_VALUE.equalsIgnoreCase(request.getHeader(HttpHeaders.CONTENT_TYPE));
    }

    private void logRequest(ContentCachingRequestWrapper request, StringBuilder requestBuilder) {

        // 请求方法
        requestBuilder.append("\n-----> [request]").append("[")
                .append("[")
                .append(MDC.get("traceId")).append("] <-----\n");
        requestBuilder.append(request.getMethod().toUpperCase()).append(" ")
                .append(request.getRequestURL().toString())
                .append(Optional.ofNullable(request.getQueryString()).map(q -> ("?" + q)).orElse(""))
                .append("\n");
        // 请求headers
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            try {
                String key = headerNames.nextElement();
                requestBuilder.append(key).append(":").append(request.getHeader(key)).append("\n");
            } catch (Exception e) {
                //ignore
            }
        }
        requestBuilder.append("\n");
        // multipartRequest body 不解析
        if (MultipartResolutionDelegate.isMultipartRequest(request)) {
            return;
        }
        // 请求content
        try {
            byte[] contentAsByteArray = request.getContentAsByteArray();
            if (!ObjectUtils.isEmpty(contentAsByteArray)) {
                String requestBody = new String(request.getContentAsByteArray(), StandardCharsets.UTF_8);
                requestBuilder.append(StringUtils.abbreviate(requestBody, 2000)).append("\n");
                return;
            }
            ServletInputStream is = request.getInputStream();
            if (!is.isFinished()) {
                String requestBody = new String(ByteStreams.toByteArray(is), StandardCharsets.UTF_8);
                requestBuilder.append(StringUtils.abbreviate(requestBody, 2000)).append("\n");
            }
        } catch (Exception e) {
            //ignore
        }
    }


    private void logResponse(ContentCachingResponseWrapper cachingResponseWrapper, StringBuilder requestAccessLogBuilder) {
        String responseString = new String(cachingResponseWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
        requestAccessLogBuilder.append("-----> [response ").append(cachingResponseWrapper.getStatus()).append("] <-----\n")
                .append(StringUtils.abbreviate(responseString, 3000)).append("\n");
    }
}
